home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C / Applications / Python 1.3 / Python 1.3 PPC / Lib / img / imgconvert.py < prev    next >
Encoding:
Python Source  |  1995-10-11  |  16.0 KB  |  557 lines  |  [TEXT/PYTH]

  1. """Module to handle conversion between in-core image formats."""
  2. #
  3. # Image-conversion helper classes and routines
  4. #
  5. import imgformat
  6. import imgcolormap
  7. import imageop
  8. import imgop
  9.  
  10. error = 'imgconvert.error'
  11. unsupported_error = 'imgconvert.unsupported_error'
  12.  
  13. HIGH_QUALITY=1
  14. TRACE=0
  15.  
  16. def setquality(onoff):
  17.     """Call with zero parameter to disable high-quality conversions"""
  18.     
  19.     global HIGH_QUALITY
  20.  
  21.     old_q = HIGH_QUALITY
  22.     HIGH_QUALITY = onoff
  23.     return old_q
  24.  
  25. def settrace(onoff):
  26.     """Call with non-zero parameter to enable conversion trace printing"""
  27.     
  28.     global TRACE
  29.  
  30.     old_t = TRACE
  31.     TRACE = onoff
  32.     return old_t
  33.  
  34. _colormaps = {}
  35.  
  36. #
  37. # getfmtcolormap - Create a colormap for an 8-bit format
  38. def getfmtcolormap(fmt):
  39.         """Return a colormap suitable for the 8-bit format argument"""
  40.     
  41.     if _colormaps.has_key(fmt):
  42.         return _colormaps[fmt]
  43.         
  44.     try:
  45.         size = fmt.descr['size']
  46.     except AttributeError:
  47.         raise error, 'Argument must be an image-format object'
  48.     if size <> 8:
  49.         raise error, 'Only 8-bit formats supported'
  50.     comp = fmt.descr['comp']
  51.     if len(comp) not in (1, 3):
  52.         raise error, 'Only 1- or 3-component formats supported'
  53.         
  54.     map = imgcolormap.new('\0\0\0\0'*256)
  55.     if len(comp) == 1:
  56.         for i in range(1<<comp[0][1]):
  57.             map[i] = (i, i, i)
  58.     else:
  59.         rmax = 1<<comp[0][1]
  60.         gmax = 1<<comp[1][1]
  61.         bmax = 1<<comp[2][1]
  62.         for r in range(rmax):
  63.             rv = r*255/(rmax-1)
  64.             for g in range(gmax):
  65.                 gv = g*255/(gmax-1)
  66.                 for b in range(bmax):
  67.                     bv = b*255/(bmax-1)
  68.                     i = (r<<comp[0][0]) | (g<<comp[1][0]) | (b<<comp[2][0])
  69.                     map[i] = (rv, gv, bv)
  70.     _colormaps[fmt] = map
  71.     return map    
  72.  
  73. #
  74. # _reverse - Convert top-to-bottom to bottom-to-top vv.
  75. #
  76. def _reverse(data, reader, srcfmt, dstfmt):
  77.     width = reader.width
  78.     height = len(data) / width
  79.     if height*width <> len(data):
  80.     raise error, 'Incorrect datasize'
  81.     rv = ''
  82.     pos = len(data)-width
  83.     while pos >= 0:
  84.     rv = rv + data[pos:pos+width]
  85.     pos = pos - width
  86.     return rv
  87.  
  88. def _reverse8(data, reader, srcfmt, dstfmt):
  89.     width = reader.width
  90.     width = (width + 3) & ~3
  91.     height = len(data) / width
  92.     if height*width <> len(data):
  93.     raise error, 'Incorrect datasize'
  94.     rv = ''
  95.     pos = len(data)-width
  96.     while pos >= 0:
  97.     rv = rv + data[pos:pos+width]
  98.     pos = pos - width
  99.     return rv
  100.  
  101. def _xreverse8(data, reader, srcfmt, dstfmt):
  102.     width = reader.width
  103.     height = len(data) / width
  104.     if height*width <> len(data):
  105.     raise error, 'Incorrect datasize'
  106.     rv = ''
  107.     pos = len(data)-width
  108.     while pos >= 0:
  109.     rv = rv + data[pos:pos+width]
  110.     pos = pos - width
  111.     return rv
  112.  
  113. #
  114. # _maptorgb - Convert colormap to rgb
  115. #
  116. def _maptorgb(data, reader, srcfmt, dstfmt):
  117.     width = reader.width
  118.     return reader.colormap.map(data, width, srcfmt, dstfmt)
  119.  
  120. #
  121. # _grey2rgb - Convert greyscale to rgb
  122. #
  123. def _greytorgb(data, reader, srcfmt, dstfmt):
  124.     greymap = getfmtcolormap(srcfmt)
  125.     # We have to trick map() in believing us...
  126.     if srcfmt == imgformat.grey:
  127.     srcfmt = imgformat.colormap
  128.     elif srcfmt == imgformat.xgrey:
  129.     srcfmt = imgformat.xcolormap
  130.     elif srcfmt == imgformat.grey_b2t:
  131.     srcfmt = imgformat.grey_b2t
  132.     return greymap.map(data, reader.width, srcfmt, dstfmt)
  133.  
  134. #
  135. # _rgb2grey - Convert rgb to greyscale
  136. #
  137. def _rgbtoxgrey(data, reader, srcfmt, dstfmt):
  138.     data = imageop.rgb2grey(data, reader.width, reader.height)
  139.     return data
  140.  
  141. #
  142. # _rgb2rgb8 - Convert rgb to xrgb8, simplistic method
  143. #
  144. #def _rgbtoxrgb8(data, reader, srcfmt, dstfmt):
  145. #    return imageop.rgb2rgb8(data, reader.width, reader.height)
  146.  
  147.     
  148. #
  149. # _shuffle - Convert various RGB formats to each other
  150. #
  151. def _shuffle(data, reader, srcfmt, dstfmt):
  152.     return imgop.shuffle(data, reader.width, reader.height, srcfmt, dstfmt)
  153.  
  154. #
  155. # _dither - Convert 8-bit grey to 1-bit grey
  156. def _dither(data, reader, srcfmt, dstfmt):
  157.     return imgop.dither(data, reader.width, reader.height, srcfmt, dstfmt)
  158.     
  159. #
  160. # _zapbits - Remove bits from an RGB color (for colormap clustering)
  161. #
  162. class Struct: pass
  163.  
  164. def _zapbits(data, reader, fmt):
  165.     # Create a new format, initially identical to the old one
  166.     newfmt = Struct()
  167.     newfmt.name = 'Scaled-down RGB format for map-clustering'
  168.     newfmt.descr = {}
  169.     for k in fmt.descr.keys():
  170.     newfmt.descr[k] = fmt.descr[k]
  171.     # Now remove one bit from each color component
  172.     components = newfmt.descr['comp']
  173.     newcomp = ()
  174.     for pos, len in components:
  175.     if len <= 1:
  176.         raise error, 'Map-clustering failed'
  177.     newcomp = newcomp + ((pos+1, len-1),)
  178.     if TRACE:
  179.     print '    Scaling RGB values to', newcomp
  180.     newfmt.descr['comp'] = newcomp
  181.     data = imgop.shuffle(data, reader.width, reader.height, fmt, newfmt)
  182.     return data, newfmt
  183.  
  184. def _scalergbdata(data, reader, srcfmt):
  185.     try:
  186.     map = imgcolormap.fromimage(data, reader.width, reader.height, srcfmt)
  187.     return data, map
  188.     except imgcolormap.error, arg:
  189.     if arg[:15] != 'Too many colors':
  190.         raise imgcolormap.error, arg
  191.     # Do color-clustering
  192.     newfmt = srcfmt
  193.     while 1:
  194.     data, newfmt = _zapbits(data, reader, newfmt)
  195.     data = imgop.shuffle(data, reader.width, reader.height,
  196.                  newfmt, srcfmt)
  197.     try:
  198.         map = imgcolormap.fromimage(data, reader.width, reader.height,
  199.                     srcfmt)
  200.         return data, map
  201.     except imgcolormap.error:
  202.         pass
  203.  
  204. #
  205. # _maprgb - Convert RGB to xcolormap format
  206. #
  207. def _maprgb(data, reader, srcfmt, dstfmt):
  208.     data, map = _scalergbdata(data, reader, srcfmt)
  209.     reader.colormap = map
  210.     data, newfmt = map.dither(data, reader.width, reader.height,
  211.                   srcfmt, HIGH_QUALITY)
  212.     if newfmt != imgformat.xcolormap:
  213.     raise error, 'Internal error: map.dither returned format '+`newfmt`
  214.     return data
  215.     
  216. def _rgbtoxrgb8(data, reader, srcfmt, dstfmt):
  217.     map = getfmtcolormap(dstfmt)
  218.     data, newfmt = map.dither(data, reader.width, reader.height,
  219.                   srcfmt, HIGH_QUALITY)
  220.     if newfmt != imgformat.xcolormap:
  221.     raise error, 'Internal error: map.dither returned format '+`newfmt`
  222.     return data
  223.     
  224. #
  225. # _removestride - Remove the stride from an 8-bit image
  226. #
  227. def _removestride(data, reader, srcfmt, dstfmt):
  228.     rv = ''
  229.     dstwidth = reader.width
  230.     srcwidth = ((dstwidth+3) & ~3)
  231.     if srcwidth == dstwidth:
  232.     return data
  233.     for i in range(0, len(data), srcwidth):
  234.     rv = rv + data[i:i+dstwidth]
  235.     return rv
  236.  
  237. #
  238. # _addstride - Add stride to an 8-bit image
  239. #
  240. def _addstride(data, reader, srcfmt, dstfmt):
  241.     rv = ''
  242.     srcwidth = reader.width
  243.     dstwidth = ((srcwidth+3) & ~3)
  244.     if srcwidth == dstwidth:
  245.     return data
  246.     extra = '\0' * (dstwidth-srcwidth)
  247.     for i in range(0, len(data), srcwidth):
  248.     rv = rv + data[i:i+srcwidth] + extra
  249.     return rv
  250.  
  251. #
  252. # 'lossiness' is a scalar value. Use 0 if nothing changes in the pixels,
  253. # 1 if no information is lost but bits are (grey->rgb), 2 if information
  254. # is lost (rgb->grey), 3 if the converter also produces a colormap.
  255. #
  256. _converters = [ \
  257.     (imgformat.grey,    imgformat.grey_b2t,    _reverse8,    0),
  258.     (imgformat.grey_b2t,imgformat.grey,        _reverse8,    0),
  259.     (imgformat.xgrey,    imgformat.xgrey_b2t,    _xreverse8,    0),
  260.     (imgformat.xgrey_b2t,imgformat.xgrey,    _xreverse8,    0),
  261.     (imgformat.colormap,imgformat.colormap_b2t,    _reverse8,    0),
  262.     (imgformat.colormap_b2t,imgformat.colormap,    _reverse8,    0),
  263.     (imgformat.rgb,    imgformat.rgb_b2t,    _reverse,    0),
  264.     (imgformat.rgb_b2t,    imgformat.rgb,        _reverse,    0),
  265.     (imgformat.rgb8,    imgformat.rgb8_b2t,    _reverse8,    0),
  266.     (imgformat.rgb8_b2t,imgformat.rgb8,        _reverse8,    0),
  267.     (imgformat.xrgb8,    imgformat.xrgb8_b2t,    _xreverse8,    0),
  268.     (imgformat.xrgb8_b2t,imgformat.xrgb8,    _xreverse8,    0),
  269.  
  270.     (imgformat.grey,    imgformat.rgb,        _greytorgb,    1),
  271.     (imgformat.xgrey,    imgformat.rgb,        _greytorgb,    1),
  272.     (imgformat.grey_b2t,imgformat.rgb_b2t,    _greytorgb,    1),
  273.  
  274.     (imgformat.colormap,imgformat.rgb,        _maptorgb,    1),
  275.     (imgformat.xcolormap,imgformat.rgb,        _maptorgb,    1),
  276.     (imgformat.colormap_b2t,imgformat.rgb_b2t,    _maptorgb,    1),
  277.  
  278.     (imgformat.rgb,    imgformat.xgrey,    _rgbtoxgrey,    2),
  279. #    (imgformat.rgb,    imgformat.xrgb8,    _rgbtoxrgb8,    2),
  280.  
  281.     (imgformat.macrgb,    imgformat.rgb,        _shuffle,    0),
  282.     (imgformat.macrgb16,imgformat.rgb,        _shuffle,    1),
  283.     (imgformat.rgb8,    imgformat.rgb,        _shuffle,    1),
  284.     (imgformat.xrgb8,    imgformat.rgb,        _shuffle,    1),
  285.     (imgformat.rgb,    imgformat.macrgb,    _shuffle,    0),
  286.     (imgformat.macrgb16,imgformat.macrgb,    _shuffle,    1),
  287.     (imgformat.rgb8,    imgformat.macrgb,    _shuffle,    1),
  288.     (imgformat.xrgb8,    imgformat.macrgb,    _shuffle,    1),
  289.     (imgformat.rgb,    imgformat.macrgb16,    _shuffle,    2),
  290.     (imgformat.macrgb,    imgformat.macrgb16,    _shuffle,    2),
  291.     (imgformat.rgb8,    imgformat.macrgb16,    _shuffle,    1),
  292.     (imgformat.xrgb8,    imgformat.macrgb16,    _shuffle,    1),
  293.  
  294.     (imgformat.rgb,    imgformat.xrgb8,    _rgbtoxrgb8,    2),
  295.  
  296.     (imgformat.xrgb8,    imgformat.rgb8,        _shuffle,    0),
  297.     (imgformat.rgb8,    imgformat.xrgb8,    _shuffle,    0),
  298.  
  299.     (imgformat.pbmbitmap, imgformat.grey,    _shuffle,    1),
  300.     (imgformat.grey,    imgformat.pbmbitmap,    _dither,    2),
  301.  
  302.     (imgformat.grey,    imgformat.xgrey,    _removestride,    0),
  303.     (imgformat.grey_b2t,imgformat.xgrey_b2t,    _removestride,    0),
  304.     (imgformat.rgb8_b2t,imgformat.xrgb8_b2t,    _removestride,    0),
  305.     (imgformat.colormap,imgformat.xcolormap,    _removestride,    0),
  306.     (imgformat.xgrey,    imgformat.grey,        _addstride,    0),
  307.     (imgformat.xgrey_b2t,imgformat.grey_b2t,    _addstride,    0),
  308.     (imgformat.xrgb8_b2t,imgformat.rgb8_b2t,    _addstride,    0),
  309.     (imgformat.xcolormap,imgformat.colormap,    _addstride,    0),
  310.  
  311.     (imgformat.rgb,    imgformat.xcolormap,    _maprgb,    3),
  312. ]
  313.  
  314. #
  315. # The converts we have built, indexed by sourceformat.
  316. # Each entry is another dictionary (indexed by dstformat).
  317. # The entries of these dictionaries are lists of [lossiness, len, [funcs]]
  318. #
  319. _generated = {}
  320.  
  321. #
  322. # Add a converter from 'srcfmt' to 'dstfmt' to the list, possibly
  323. # replacing an existing converter
  324. #
  325. def addconverter(srcfmt, dstfmt, func, lossy):
  326.         """Tell imgconvert about a new converter.
  327.     Args: source_format, dest_format, function, lossy.
  328.     lossy is 0 (not lossy), 1 (wastes bits), 2 (loses bits) or
  329.     3 (converts to colormap format).
  330.  
  331.     function is called as function(data, reader, srcfmt, dstfmt)
  332.     """
  333.     
  334.     for i in range(len(_converters)):
  335.         isrcfmt, idstfmt, irtn, ilossy = _converters[i]
  336.         if (srcfmt, dstfmt) == (isrcfmt, idstfmt):
  337.             _converters[i] = (srcfmt, dstfmt, func, lossy)
  338.             return
  339.     _converters.append((srcfmt,dstfmt,func,lossy))
  340.  
  341. #
  342. # Returns a list of conversion functions that will convert
  343. # srcfmt to dstfmt if applied in that order.
  344. #
  345. def getconverter(srcfmt, dstfmt):
  346.         """Return a converter from srcfmt to dstfmt.
  347.     A converter is a list [lossy, length, list-of-tuples],
  348.     where each tuple is (srcfmt, dstfmt, func, lossy).
  349.     Calling each of the functions in order will convert your image.
  350.     """
  351.     
  352.         global _generated
  353.     #
  354.     # If formats are the same return the dummy converter
  355.     #
  356.     if srcfmt == dstfmt: return []
  357.     #
  358.     # Otherwise, if we have a converter, return that one
  359.     #
  360.     for this in _converters:
  361.             isrcfmt, idstfmt, irtn, ilossy = this
  362.         if (srcfmt, dstfmt) == (isrcfmt, idstfmt):
  363.             return [ilossy, 1, [this]]
  364.     #
  365.     # Finally, we try to create a converter
  366.     #
  367.     if not _generated.has_key(srcfmt):
  368.             # Not there yet. Try to create it.
  369.         _generated[srcfmt] = _enumerate_converters(srcfmt)
  370.         
  371.     if not _generated[srcfmt].has_key(dstfmt):
  372.         raise unsupported_error, (srcfmt, dstfmt)
  373.  
  374.     cf = _generated[srcfmt][dstfmt]
  375.     return cf
  376.  
  377. def _enumerate_converters(srcfmt):
  378.     cvs = {}
  379.     formats = [srcfmt]
  380.     steps = 0
  381.     while 1:
  382.         workdone = 0
  383.         for this in _converters:
  384.                 isrcfmt, idstfmt, irtn, ilossy = this
  385.                 #
  386.             # First see if the source format is of any use.
  387.             #
  388.             if isrcfmt == srcfmt:
  389.                     #
  390.                 # This converter directly understands our
  391.                 # source format. Remember it.
  392.                 #
  393.                 template = [ilossy, 1, [this]]
  394.             elif cvs.has_key(isrcfmt):
  395.                     #
  396.                 # We have a path to this format, so
  397.                 # this converter can help us further.
  398.                 #
  399.                 template = cvs[isrcfmt][:]
  400.                 template[0] = max(template[0], ilossy)
  401.                 template[1] = template[1] + 1
  402.                 template[2] = template[2] + [this]
  403.             else:
  404.                 continue
  405.             #
  406.             # Next, check whether we want this converter
  407.             # (if it is the first one for this dstfmt, or
  408.             # if it is better than what we have)
  409.             #
  410.             if not cvs.has_key(idstfmt):
  411.                 cvs[idstfmt] = template
  412.                 workdone = 1
  413.             else:
  414.                 previous = cvs[idstfmt]
  415.                 if template < previous:
  416.                     cvs[idstfmt] = template
  417.                     workdone = 1
  418.         if not workdone:
  419.             break
  420.         #
  421.         # Finally, a check for loops.
  422.         #
  423.         steps = steps + 1
  424.         if steps > len(_converters):
  425.             print '------------------loop in emunerate_converters--------'
  426.             print 'CONVERTERS:'
  427.             print _converters
  428.             print 'RESULTS:'
  429.             print cvs
  430.             raise error, 'Internal error - loop'
  431.     return cvs
  432.  
  433. def stackreader(dstfmt, reader):
  434.     """Create a reader-like object that reads image file data and
  435.     converts it to the requested format.
  436.     Args: format, original_reader
  437.     """
  438.     
  439.     if dstfmt in reader.format_choices:
  440.     reader.format = dstfmt
  441.     return reader
  442.     # Nope, not supported directly. Find all possible converters
  443.     list = []
  444.     for srcfmt in reader.format_choices:
  445.     try:
  446.         rv = getconverter(srcfmt, dstfmt)
  447.     except unsupported_error:
  448.         continue
  449.     if rv:
  450.         [lossy, len, funclist] = rv
  451.         list.append(lossy, len, srcfmt, funclist)
  452.     if not list:
  453.     raise unsupported_error, (reader.format_choices, dstfmt)
  454.     # Now, sort and use the best
  455.     list.sort()
  456.     lossy, len, srcfmt, funclist = list[0]
  457.     if lossy == 3:
  458.     return _MapReaderStack(reader, dstfmt, srcfmt, funclist)
  459.     else:
  460.     return _ConverterStack(reader, dstfmt, srcfmt, funclist)
  461.  
  462. def stackwriter(srcfmt, writer):
  463.     """Create a writer-like object that writes an image file from source
  464.     data in the specified format.
  465.     Args: source_format, destination_writer
  466.     """
  467.     
  468.     if srcfmt in writer.format_choices:
  469.     writer.format = srcfmt
  470.     return writer
  471.     # Nope, not supported directly. Find all possible converters
  472.     list = []
  473.     for dstfmt in writer.format_choices:
  474.     try:
  475.         [lossy, len, funclist] = getconverter(srcfmt, dstfmt)
  476.     except unsupported_error:
  477.         continue
  478.     list.append(lossy, len, dstfmt, funclist)
  479.     if not list:
  480.     raise unsupported_error, (srcfmt, writer.format_choices)
  481.     # Now, sort and use the best
  482.     list.sort()
  483.     lossy, len, dstfmt, funclist = list[0]
  484.     return _ConverterStack(writer, srcfmt, dstfmt, funclist)
  485.  
  486. #
  487. # The placeholder class
  488. class _ConverterStack:
  489.     def __init__(self, base, ourfmt, basefmt, funclist):
  490.     self._base = base
  491.     self._funclist = funclist
  492.     self._copyattrtoself()
  493.     self.format_choices = (ourfmt,)
  494.     self.format = ourfmt
  495.     self._basefmt = basefmt
  496.  
  497.     def _copyattrtoself(self):
  498.     srcdict = self._base.__dict__
  499.     dstdict = self.__dict__
  500.     for k in srcdict.keys():
  501.         if k[0] <> '_':
  502.         dstdict[k] = srcdict[k]
  503.  
  504.     def _copyattrfromself(self):
  505.     srcdict = self.__dict__
  506.     dstdict = self._base.__dict__
  507.     for k in srcdict.keys():
  508.         if k[0] <> '_':
  509.         dstdict[k] = srcdict[k]
  510.  
  511.     def read(self):
  512.     self._copyattrfromself()
  513.     self._base.format = self._basefmt
  514.     data = self._base.read()
  515.     if TRACE:
  516.         print 'Converting', self._basefmt.name, 'to', self.format.name,
  517.         print 'in', len(self._funclist), 'steps:'
  518.     for f in self._funclist:
  519.         if TRACE:
  520.         print '  ',f[0].name, 'to',f[1].name
  521.         data = apply(f[2], (data, self, f[0], f[1]))
  522.     return data
  523.  
  524.     def write(self, data):
  525.     if TRACE:
  526.         print 'Converting', self.format.name, 'to', self._basefmt.name,
  527.         print 'in', len(self._funclist), 'steps:'
  528.     for f in self._funclist:
  529.         if TRACE:
  530.         print '  ',f[0].name, 'to',f[1].name
  531.         data = apply(f[2], (data, self, f[0], f[1]))
  532.     self._copyattrfromself()
  533.     self._base.format = self._basefmt
  534.     self._base.write(data)
  535.  
  536. #
  537. # A MapReaderStack is used if one of the converters also returns a
  538. # colormap. In this case we have to read upon init to set the colormap
  539. # attribute.
  540. #
  541. class _MapReaderStack(_ConverterStack):
  542.     def __init__(self, base, ourfmt, basefmt, funclist):
  543.     _ConverterStack.__init__(self, base, ourfmt, basefmt, funclist)
  544.     self._base.format = self._basefmt
  545.     data = self._base.read()
  546.     if TRACE:
  547.         print 'Converting', self._basefmt.name, 'to', self.format.name,
  548.         print 'in', len(self._funclist), 'steps:'
  549.     for f in self._funclist:
  550.         if TRACE:
  551.         print '  ',f[0].name, 'to',f[1].name
  552.         data = apply(f[2], (data, self, f[0], f[1]))
  553.     self._data = data
  554.  
  555.     def read(self):
  556.     return self._data
  557.